#include "HiFileSender.h"
#include <QNetworkProxy>
#include <QThread>
#include "HiCmdPackaging.h"
#include "HiCmdUnpacking.h"
#include <QDataStream>
#include <QJsonDocument>

HiFileSender::HiFileSender(const QString& ip, int port, QObject* parent /*= nullptr*/)
	:QObject(parent), mIP(ip), mPort(port)
{
	mId = QUuid::createUuid().toString();
	mInternalSignals = new HiFileSender_InternalSignals(this);
	connect(mInternalSignals, &HiFileSender_InternalSignals::signalSend, this, &HiFileSender::onSignalSend);
}

HiFileSender::~HiFileSender()
{
	if (mInternalSignals)
	{
		delete mInternalSignals;
		mInternalSignals = nullptr;
	}
	if (mFileSocket)
	{
		mFileSocket->disconnectFromHost();
		delete mFileSocket;
		mFileSocket = nullptr;
	}
}

QString HiFileSender::getId() const
{
	return mId;
}

void HiFileSender::send(const QList<HiFileTransferDef::sFile>& files)
{
	emit mInternalSignals->signalSend(files);
}

void HiFileSender::stop()
{
	setSendState(HiFileTransferDef::eSendState_Stop);
}

void HiFileSender::onSignalSend(const QList<HiFileTransferDef::sFile>& internalFiles)
{
	QList<HiFileTransferDef::sFile> copyInternalFiles = internalFiles;
	for (HiFileTransferDef::sFile& internalFile : copyInternalFiles)
	{
		QFile file(internalFile.filePath);
		if (file.exists())
		{
			internalFile.fileSize = file.size();
			mTotalSize += internalFile.fileSize;
			mFiles.append(internalFile);
		}
	}
	sendNext();
}

QString HiFileSender::getLastErrorString(void)
{
	return mErrorString;
}

void HiFileSender::setSendState(HiFileTransferDef::eSendState state)
{
	QMutexLocker locker(&mSendStateLock);
	if (mSendState != state)
	{
		mSendState = state;
	}
}

HiFileTransferDef::eSendState HiFileSender::getSendState()
{
	QMutexLocker locker(&mSendStateLock);
	return mSendState;
}

HiFileTransferDef::sFile HiFileSender::nextFile()
{
	auto initFile = [](HiFileTransferDef::sFile& file) {
		file = HiFileTransferDef::sFile();
		return true;
	};
	auto notFindFile = [](const HiFileTransferDef::sFile& file, qint64& throwSize) {
		throwSize += file.fileSize;
		return true; 
	};

	HiFileTransferDef::sFile file;
	qint64 throwSize = 0;
	{
		QMutexLocker locker(&mFilesLock);
		if (mFiles.size() == 0)
			return file;

		do { 
			file = mFiles.takeFirst();
		} 
		while (initFile(file) || mFiles.size() > 0 
			|| file.filePath.isEmpty() || !QFile(file.filePath).exists() || notFindFile(file, throwSize));
	}
	mFinishedSize += throwSize;
	return file;
}

void HiFileSender::sendNext()
{
	if (mFileSocket)
		return;

	HiFileTransferDef::sFile file = nextFile();
	if (file.filePath.isEmpty())
	{
		emit signalSendAllFinished();
		return;
	}

	mCurrentInternalFile = file;

	mFileSocket = new QTcpSocket(this);
	mFileSocket->setProxy(QNetworkProxy::NoProxy);
	connect(mFileSocket, &QTcpSocket::connected, this, &HiFileSender::onSocketConnected);
	connect(mFileSocket, &QTcpSocket::bytesWritten, this, &HiFileSender::sendOnceBuff);
	connect(mFileSocket, &QTcpSocket::disconnected, this, &HiFileSender::onSocketError);
	connect(mFileSocket, &QTcpSocket::errorOccurred, this, &HiFileSender::onSocketError);
	mFileSocket->connectToHost(mIP, mPort);

	emit signalSendChanged(file);
}

void HiFileSender::sendOnceBuff()
{
	qint64 remainSize = mCurrentFile.size() - mCurrentFile.pos();
	qint64 sendSize = qMin(remainSize, mOnceSendSize);
	mFinishedSize += sendSize;
	QByteArray buff = mCurrentFile.read(sendSize);
	mFileSocket->write(buff);
	emit signalSendPrograss(mTotalSize, mFinishedSize);
	if (mCurrentFile.atEnd())
	{
		disconnect(mFileSocket, 0, this, 0);
		mFileSocket->deleteLater();
		mFileSocket = nullptr;
		emit signalSendFinished(mCurrentInternalFile);
		sendNext();
	}
}

void HiFileSender::onSocketError()
{
	{
		QMutexLocker locker(&mFilesLock);
		mFiles.clear();
	}
	disconnect(mFileSocket, 0, this, 0);
	mFileSocket->deleteLater();
	mFileSocket = nullptr;
	mErrorString = tr("socket error");
	emit signalSendError(mErrorString);
	emit signalClose();
}

void HiFileSender::onSocketConnected()
{
	mCurrentFile.setFileName(mCurrentInternalFile.filePath);
	if (!mCurrentFile.open(QFile::ReadOnly))
	{
		onFileOpenError();
		return;
	}
	QByteArray inParam;
	QDataStream inStream(&inParam, QIODevice::ReadOnly);
	inStream << mCurrentInternalFile.saveRelativePath << mCurrentInternalFile.userData
		<< mCurrentInternalFile.fileSize << mId;
	mFileSocket->write(HiCmdPackaging::toStream(HiFileTransferDef::eSendFileCmd_Start, inParam));
	sendOnceBuff();
}

void HiFileSender::onMessageReceived()
{
	int nCmdType = -1;
	QByteArray rParam;
	HiCmdUnpacking::eResult eUnpackingResult = HiCmdUnpacking::takeCmd(mFileSocket, nCmdType, rParam);
	switch (eUnpackingResult)
	{
	case HiCmdUnpacking::eResult::eSuccess:
	{
		switch (nCmdType)
		{
		case HiFileTransferDef::eRevFileCmd_Start:
		{
			// TODO
			break;
		}
		case HiFileTransferDef::eRevFileCmd_Error:
		{
			QString errorString = QJsonDocument::fromJson(rParam).object().value("msg").toString();
			emit signalSendError(errorString);
			mFileSocket->disconnectFromHost();
			break;
		}
		default:
		{
			break;
		}
		}
	}
	break;
	case HiCmdUnpacking::eResult::eLess:
		return;
	case HiCmdUnpacking::eResult::eStartFlagError:
	case HiCmdUnpacking::eResult::eVersionError:
	case HiCmdUnpacking::eResult::eCrcError:
	default:
	{
		mFileSocket->read(1);
	}
	break;
	}
	if (mFileSocket->bytesAvailable() > 0)
		onMessageReceived();
}

void HiFileSender::onFileOpenError()
{
	{
		QMutexLocker locker(&mFilesLock);
		mFiles.clear();
	}
	disconnect(mFileSocket, 0, this, 0);
	mFileSocket->deleteLater();
	mFileSocket = nullptr;
	mErrorString = tr("file open error");
	emit signalSendError(mErrorString);
}




/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
QMutex HiFileSenderManager::mLock;
HiFileSenderManager* HiFileSenderManager::mInstance = nullptr;
HiFileSenderManager::HiFileSenderManager()
{

}

HiFileSenderManager::~HiFileSenderManager()
{
	QMap<QString, HiFileSender*> copyIdAndReceiverMap = mIdAndSenderMap;
	for (auto itr = copyIdAndReceiverMap.begin(); itr != copyIdAndReceiverMap.end(); itr++)
	{
		destroySender(itr.value());
	}
}

HiFileSenderManager* HiFileSenderManager::getInstance()
{
	QMutexLocker locker(&mLock);
	if (mInstance == nullptr)
	{
        mInstance = new HiFileSenderManager();
	}
	return mInstance;
}

HiFileSender* HiFileSenderManager::createSender(const QString& ip, int port)
{
	QThread* thread = new QThread();
	thread->setObjectName("HiFileCommunicationTcpSocket_Thread");
	HiFileSender* sender = new HiFileSender(ip, port);
	connect(thread, &QThread::finished, thread, &QThread::deleteLater);
	connect(thread, &QThread::finished, sender, &HiFileSender::deleteLater);
	sender->moveToThread(thread);
	mSenderAndThreadMap.insert(sender, thread);
	thread->start();
	return sender;
}

void HiFileSenderManager::destroySender(HiFileSender* sender)
{
	if (sender == nullptr || !mSenderAndThreadMap.contains(sender))
		return;
	QThread* thread = mSenderAndThreadMap.take(sender);
	mIdAndSenderMap.remove(sender->getId());
	if (thread == nullptr)
	{
		Q_ASSERT(false);
	}
	thread->quit();
}

QMap<QString, HiFileSender*> HiFileSenderManager::getAllSenders()
{
	return mIdAndSenderMap;
}

QMap<QString, HiFileSender*>::const_iterator HiFileSenderManager::getAllSendersBeginIterator()
{
	return mIdAndSenderMap.begin();
}

HiFileSenderManager* getFileSenderManager()
{
	return HiFileSenderManager::getInstance();
}